package com.editor.smsmanager;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;

import androidx.annotation.Keep;

import com.editor.smsmanager.utils.MyLog;
import com.google.gson.Gson;

import org.java_websocket.WebSocket;
import org.webrtc.CandidatePairChangeEvent;
import org.webrtc.DataChannel;
import org.webrtc.EglBase;
import org.webrtc.HardwareVideoDecoderFactory;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.MediaStreamTrack;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.RtpReceiver;
import org.webrtc.RtpTransceiver;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import org.webrtc.SoftwareVideoDecoderFactory;
import org.webrtc.SoftwareVideoEncoderFactory;
import org.webrtc.VideoFrame;
import org.webrtc.VideoSink;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


public class PeerConnectHelper {
    private static final String TAG = "PeerConnectHelper";
    private static final String VIDEO_TRACK_ID = "ebo_video_track";

    private long time = 0L;

    Context context;
    PeerConnection peerConnection;
    DataChannel audioDataChannel;
    DataChannel videoDataChannel;
    SpdServer spdServer;
    WebrtcRenderer webrtcRenderer;

    Handler mainHandler = new Handler(Looper.getMainLooper());

    List<PeerConnection.IceServer> iceServers = new ArrayList<>();

    public PeerConnectHelper(Context context, SpdServer spdServer, WebrtcRenderer webrtcRenderer) {
        this.context = context;
        this.spdServer = spdServer;
        this.webrtcRenderer = webrtcRenderer;

        EglBase eglBase = EglBase.create();

        webrtcRenderer.init(eglBase.getEglBaseContext(), null);
        webrtcRenderer.setKeepScreenOn(true);
        webrtcRenderer.setMirror(false);

        spdServer.setOnReceiveMessage(new SpdServer.onReceiveMessage() {
            @Override
            public void onMessage(WebSocket conn, String message) {
                try {
                    SpdJson spdJson = new Gson().fromJson(message, SpdJson.class);
                    if(spdJson != null){
                        MyLog.E(TAG,"onMessage spdJson.type="+spdJson.type);
                        if(TextUtils.equals(spdJson.type, "offer")){
                            setRemoteDescription(spdJson.sdp);
                        }else if(TextUtils.equals(spdJson.type, "candidate")){
                            addIce(spdJson);
                        }
                    }else{
                        MyLog.E(TAG,"onMessage spdJson is NULL");
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }

            }
        });
    }

    @Keep
    public PeerConnection.PeerConnectionState getConnectionStatus() {
        if (peerConnection == null) {
            return PeerConnection.PeerConnectionState.NEW;
        } else {
            return peerConnection.connectionState();
        }
    }

    @Keep
    public PeerConnection.IceConnectionState getIceConnectionStatus() {
        if (peerConnection == null) {
            return PeerConnection.IceConnectionState.NEW;
        } else {
            return peerConnection.iceConnectionState();
        }
    }

    private PeerConnectionFactory createConnectionFactory() {
        String fieldTrials = "";
//        fieldTrials += "WebRTC-SpsPpsIdrIsH264Keyframe/Enabled/";
//        fieldTrials += "WebRTC-FlexFEC-03-Advertised/Enabled/";
//        fieldTrials += "WebRTC-FlexFEC-03/Enabled/";

        PeerConnectionFactory.initialize(
                PeerConnectionFactory.InitializationOptions.builder(context)
                        .setEnableInternalTracer(true)
                        .setFieldTrials(fieldTrials)
                        .createInitializationOptions());

        PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
//  conf_.kvsRtcConfiguration.generateRSACertificate = 1;
        return PeerConnectionFactory.builder()
                .setOptions(options)
                .setVideoEncoderFactory(new SoftwareVideoEncoderFactory())
                .setVideoDecoderFactory(new SoftwareVideoDecoderFactory())
                .setVideoDecoderFactory(new HardwareVideoDecoderFactory())
                .createPeerConnectionFactory();
    }

    private void createPeerConnection() {
//        if (peerConnection == null) {
//            iceServers.clear();
            //iceServers.add(PeerConnection.IceServer.builder("turn:120.25.249.33:8123").setUsername("mt_user").setPassword("mt_void_123").createIceServer());
            iceServers.add(PeerConnection.IceServer.builder("stun:stun.xten.com:3478").createIceServer());
            iceServers.add(PeerConnection.IceServer.builder("stun:77.72.169.213:3478").createIceServer());


            for (PeerConnection.IceServer iceServer : iceServers) {
                MyLog.D(TAG, "iceServer=" + iceServer);
            }

            PeerConnectionFactory factory = createConnectionFactory();
            PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);
            rtcConfig.enableDtlsSrtp = true;

            rtcConfig.iceTransportsType = PeerConnection.IceTransportsType.ALL;
            //rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
//            List<String> mediaStreamLabels = Collections.singletonList("stream_id");

            //conf_.iceTransportPolicy = ICE_TRANSPORT_POLICY::ICE_TRANSPORT_POLICY_ALL;
            //conf_.kvsRtcConfiguration.generateRSACertificate = 1;

            peerConnection = factory.createPeerConnection(rtcConfig, connectObserver);
//            if (peerConnection != null) {
//                List<String> mediaStreams = Collections.singletonList("stream_id");
//                RtpTransceiver.RtpTransceiverDirection dir = RtpTransceiver.RtpTransceiverDirection.SEND_RECV;
//                RtpTransceiver.RtpTransceiverInit rti =
//                        new RtpTransceiver.RtpTransceiverInit(dir, mediaStreams);
//                peerConnection.addTransceiver(MediaStreamTrack.MediaType.MEDIA_TYPE_VIDEO, rti);
//            }
//        }
    }

    @Keep
    public void setRemoteDescription(String sdp) {
        MyLog.D(TAG, "setRemoteDescription Offer");

        createPeerConnection();

        SessionDescription desc = new SessionDescription(SessionDescription.Type.OFFER, sdp);

        peerConnection.setRemoteDescription(new SdpObserver() {
            @Override
            public void onCreateSuccess(SessionDescription sessionDescription) {

            }

            @Override
            public void onSetSuccess() {
                MyLog.D(TAG, "setRemoteDescription onSetSuccess");
                MediaConstraints mediaConstraints = new MediaConstraints();
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
        //mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));

                peerConnection.createAnswer(new SdpListener() {
                    @Override
                    public void onCreateSuccess(SessionDescription sessionDescription) {
                        MyLog.I(TAG,"createAnswer");
                        peerConnection.setLocalDescription(new SdpObserver() {
                            @Override
                            public void onCreateSuccess(SessionDescription sessionDescription) {
                                MyLog.I(TAG,"setLocalDescription onCreateSuccess");
                            }

                            @Override
                            public void onSetSuccess() {
                                MyLog.I(TAG,"setLocalDescription onSetSuccess");
                                String sdp = peerConnection.getLocalDescription().description;
                                SpdJson spdJson = new SpdJson("answer");
                                spdJson.sdp = sdp;
                                spdServer.sendSpdJson(spdJson);
                            }

                            @Override
                            public void onCreateFailure(String s) {
                                MyLog.E(TAG,"setLocalDescription onCreateFailure:"+s);
                            }

                            @Override
                            public void onSetFailure(String s) {
                                MyLog.E(TAG,"setLocalDescription onSetFailure:"+s);
                            }
                        }, sessionDescription);
                    }
                }, mediaConstraints);
            }

            @Override
            public void onCreateFailure(String s) {

            }

            @Override
            public void onSetFailure(String s) {

            }
        }, desc);


    }

    @Keep
    public void startConnect(/*EnaBotPlayer enaBotPlayer,*/ List<PeerConnection.IceServer> iceServers) {
//        this.enaBotPlayer = enaBotPlayer;
        this.iceServers = iceServers;
//        createPeerConnection();
//        MediaConstraints mediaConstraints = new MediaConstraints();
//        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
//        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
//        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
//        peerConnection.createOffer(new SdpListener() {
//            @Override
//            public void onCreateSuccess(SessionDescription sessionDescription) {
//                peerConnection.setLocalDescription(new SdpListener() {
//                    @Override
//                    public void onSetSuccess() {
//                        String sdp = peerConnection.getLocalDescription().description;
//                        commandHelper.sendLocalSdp(sdp);
//                        Log.d(TAG, "Send Local SDP:" + sdp);
//                    }
//                }, sessionDescription);
//            }
//        }, mediaConstraints);
    }

    @Keep
    public void stopConnect(Boolean needStop) {
        if (needStop) {
        }

        release();
    }

    @Keep
    public void release() {
        try {
            if (peerConnection != null) {
                peerConnection.close();
                peerConnection = null;
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public void createDataChannel() {
        if (audioDataChannel == null) {
            audioDataChannel = peerConnection.createDataChannel("audio", getDataChannelInit(102));
            audioDataChannel.registerObserver(audioDataObserver);
        }

        if (videoDataChannel == null) {
            videoDataChannel = peerConnection.createDataChannel("video", getDataChannelInit(101));
            videoDataChannel.registerObserver(videoDataObserver);
        }
    }

    private DataChannel.Init getDataChannelInit(int id) {
        DataChannel.Init init = new DataChannel.Init();
        init.ordered = true;
//        init.negotiated = false;
//        init.maxRetransmits = 0;
//        init.maxRetransmitTimeMs = -1;
        init.id = id;
//        init.protocol = "";
        return init;
    }

    private final PeerConnection.Observer connectObserver = new PeerConnection.Observer() {

        @Override
        public void onSelectedCandidatePairChanged(CandidatePairChangeEvent event) {
            MyLog.E(TAG, event.local.sdp);
            MyLog.E(TAG, event.remote.sdp);
            boolean isP2p = !(event.local.sdp.contains("relay") || event.remote.sdp.contains("relay"));
            MyLog.E(TAG, "onSelectedCandidatePairChanged isP2p="+isP2p);
        }

        @Override
        public void onSignalingChange(PeerConnection.SignalingState signalingState) {
        }

        @Override
        public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
            MyLog.W(TAG, "onIceConnectionChange iceConnectionState=" + iceConnectionState);
        }

        @Override
        public void onIceConnectionReceivingChange(boolean b) {
            MyLog.D(TAG, "onIceConnectionReceivingChange b=" + b);
        }

        @Override
        public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
            MyLog.D(TAG, "onIceGatheringChange iceGatheringState=" + iceGatheringState);
        }

        @Override
        public void onIceCandidate(IceCandidate iceCandidate) {
            MyLog.D(TAG, "onIceCandidate iceCandidate=" + iceCandidate.sdp);
            SpdJson spdJson = new SpdJson("candidate");

            spdJson.candidate = iceCandidate.sdp;
            spdJson.sdpMid = iceCandidate.sdpMid;
            spdJson.sdpMLineIndex = iceCandidate.sdpMLineIndex;

            spdServer.sendSpdJson(spdJson);

            //记录第一个ice的时间   当收到relay或第一个ice拿到3s后,直接发送连接指令
//            if (time == 0) time = System.currentTimeMillis();
//            if (iceCandidate.sdp.contains("relay") || System.currentTimeMillis() - time > 3 * 1000) {
//                handler.removeCallbacksAndMessages(null);
//                handler.post(() -> {
//                    time = 0L;
//                    commandHelper.startPeerConnect(peerConnection.getLocalDescription().description);
//                });
//            }
        }

        @Override
        public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {

        }

        @Override
        public void onAddStream(MediaStream mediaStream) {
            MyLog.E(TAG, "onAddStream");
        }

        @Override
        public void onRemoveStream(MediaStream mediaStream) {

        }

        @Override
        public void onDataChannel(DataChannel channel) {
            MyLog.D(TAG, "onDataChannel label()=" + channel.label());

            audioDataChannel = channel;
            audioDataChannel.registerObserver(audioDataObserver);
        }

        @Override
        public void onRenegotiationNeeded() {

        }

        @Override
        public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
            MyLog.E(TAG, "onAddTrack");
            VideoTrack track = (VideoTrack) rtpReceiver.track();
            if (track != null) {
                //track.addSink(videoFrame -> MyLog.E(TAG, "VideoSink onFrame"+videoFrame.getTimestampNs()));
                track.addSink(webrtcRenderer);
//                track.addSink(PeerConnectHelper.this);
//                iotcClient.startVideoDecode(enaBotPlayer, videoDecodeListener);
//                h264Unpack = new H264Unpack(h264FrameCallback);
            }
        }

        @Override
        public void onTrack(RtpTransceiver transceiver) {
            MyLog.E(TAG, "onTrack");
        }

        @Override
        public void onConnectionChange(PeerConnection.PeerConnectionState newState) {
            MyLog.D(TAG, "onConnectionChange PeerConnectionState=" + newState);
        }
    };

    private long lastAudioMessage = 0;

    private DataChannel.Observer audioDataObserver = new DataChannel.Observer() {
        @Override
        public void onBufferedAmountChange(long l) {
        }

        @Override
        public void onStateChange() {
            MyLog.D(TAG, "audioDataChannel onStateChange=" + audioDataChannel.state());
            ByteBuffer buffer = ByteBuffer.wrap("DataChannel Hello".getBytes());
            audioDataChannel.send(new DataChannel.Buffer(buffer, true));
        }

        @Override
        public void onMessage(DataChannel.Buffer buffer) {
            if (buffer.binary) {
                long delta = System.currentTimeMillis() - lastAudioMessage;
                if (delta > 200) {
                    MyLog.E(TAG, "audioDataChannel binary data, length=" + buffer.data.remaining() + ",delta=" + (System.currentTimeMillis() - lastAudioMessage) + "ms");
                } else {
                    MyLog.D(TAG, "audioDataChannel binary data, length=" + buffer.data.remaining() + ",delta=" + (System.currentTimeMillis() - lastAudioMessage) + "ms");
                }

                lastAudioMessage = System.currentTimeMillis();
            } else {
                ByteBuffer byteBuffer = buffer.data;
                byte[] buf = new byte[byteBuffer.remaining()];
                byteBuffer.get(buf);
                MyLog.D(TAG, "audioDataChannel onMessage=" + new String(buf));
            }
        }
    };

    private long lastVideoMessage = 0;
    private DataChannel.Observer videoDataObserver = new DataChannel.Observer() {
        @Override
        public void onBufferedAmountChange(long l) {
        }

        @Override
        public void onStateChange() {
            MyLog.D(TAG, "videoDataChannel onStateChange=" + videoDataChannel.state());

            if (videoDataChannel.state() == DataChannel.State.OPEN) {
//                commandHelper.connectVideo(true);
//                iotcClient.startVideoDecode(enaBotPlayer, videoDecodeListener);
//                h264Unpack = new H264Unpack(h264FrameCallback);
            }
        }

        @Override
        public void onMessage(DataChannel.Buffer buffer) {

        }
    };

    public void sendAudioData(byte[] buf) {
        if (audioDataChannel != null) {
            ByteBuffer byteBuffer = ByteBuffer.wrap(buf);
            DataChannel.Buffer buffer = new DataChannel.Buffer(byteBuffer, true);
            audioDataChannel.send(buffer);
        }
    }

    public void sendDataChannel(String data) {
        if (audioDataChannel != null) {
            ByteBuffer byteBuffer = ByteBuffer.wrap(data.getBytes());
            DataChannel.Buffer buffer = new DataChannel.Buffer(byteBuffer, false);
            audioDataChannel.send(buffer);
        }
    }


    @Keep
    public class VideoTime {
        String time;
        String data;
    }



    @Keep
    public void addIce(SpdJson spdJson) {
        IceCandidate iceCandidate = new IceCandidate(spdJson.sdpMid, spdJson.sdpMLineIndex, spdJson.candidate);
        peerConnection.addIceCandidate(iceCandidate);
    }
}
